﻿using SharpDepend.WinRT.Classes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;

namespace SharpDepend.WinRT
{
    internal class ClientManager : ISocketClientManager
    {
        /// <summary>
        /// Reference: http://stackoverflow.com/questions/18824013/c-sharp-winrt-c-sharp-wpf-connection-with-tcp
        /// Reference: http://stackoverflow.com/questions/29020817/streamsocket-on-winrt-doesnt-receive-all-data
        /// </summary>
        internal class Client : ISocketClient
        {
            private StreamSocket mClient;
            private bool mConnected;
            private List<byte> mFeatchedData;

            public bool IsConnected
            {
                get
                {
                    return mConnected;
                }
            }

            public Client()
            {
                mClient = new StreamSocket();
                mFeatchedData = new List<byte>();
            }

            public Client(StreamSocket client)
            {
                mClient = client;
            }

            public void Connect(string ip, int port)
            {
                IAsyncAction handle = mClient.ConnectAsync(
                         new Windows.Networking.HostName(ip),
                         port.ToString(),
                         SocketProtectionLevel.PlainSocket);

                Helper.WaitToComplete(handle);

                if (handle.ErrorCode == null)
                {
                    mConnected = true;
                }
                else
                {
                    throw handle.ErrorCode;
                }
            }

            public void Write(byte[] data, int offset, int length)
            {
                DataWriter writer = new DataWriter(mClient.OutputStream);

                if (offset == 0 && data.Length == length)
                {
                    writer.WriteBytes(data);
                    var handle = writer.StoreAsync();
                    Helper.WaitToComplete(handle);
                }
                else
                {
                    // TODO: Optimize.
                    byte[] peace = new byte[length];
                    Array.Copy(data, peace, length);
                    writer.WriteBytes(peace);
                    var handle = writer.StoreAsync();
                    Helper.WaitToComplete(handle);
                }
                
                writer.DetachBuffer();
                writer.DetachStream();
            }

            private void FeatchData(int want)
            {
                if (mFeatchedData.Count < want)
                {
                    DataReader reader = new DataReader(mClient.InputStream);
                    reader.InputStreamOptions = InputStreamOptions.Partial;
                    const uint MAX_BUFFER = 8 * 1024;
                    uint returnBuffer = 0;
                    do
                    {
                        var handle = reader.LoadAsync(MAX_BUFFER);

                        Helper.WaitToComplete(handle);

                        returnBuffer = handle.GetResults();

                        byte[] data = new byte[reader.UnconsumedBufferLength];

                        reader.ReadBytes(data);

                        mFeatchedData.AddRange(data);

                    } while (returnBuffer >= MAX_BUFFER);

                    reader.DetachBuffer();
                    reader.DetachStream();
                }
            }

            public int GetAvailable()
            {
                if (mFeatchedData.Count > 0)
                {
                    // Data already exist.
                    return mFeatchedData.Count;
                }
                else
                {
                    FeatchData(1);
                }

                return mFeatchedData.Count;
            }

            public int ReadByte()
            {
                try
                {
                    FeatchData(1);

                    if (mFeatchedData.Count == 0)
                    {
                        return -1;
                    }
                    else
                    {
                        int b = mFeatchedData[0];
                        mFeatchedData.RemoveAt(0);
                        return b;
                    }
                }
                catch (Exception)
                {
                    mConnected = false;
                    return -1;
                }
            }

            public void Read(byte[] data, int offset, int length)
            {
                FeatchData(length);

                if (mFeatchedData.Count > 0)
                {
                    if (length > mFeatchedData.Count)
                    {
                        throw new IndexOutOfRangeException("Can not read more data than unconsumed buffer size. GetAvailable() should be readed first.");
                    }
                }

                Array.Copy(mFeatchedData.ToArray(), 0, data, offset, length);

                mFeatchedData.RemoveRange(0, length);
            }

            public void Close()
            {
                mConnected = false;
                mClient.Dispose();
                mClient = null;
            }

            public string IP
            {
                get
                {
                    return mClient.Information.RemoteAddress.DisplayName;
                }
            }
        }

        public ISocketClient CreateClient()
        {
            return new Client();
        }
    }
}

